home *** CD-ROM | disk | FTP | other *** search
/ Enter 2006 September / Enter 09 2006.iso / Internet / SpamExperts Home 1.1 / SpamExperts Home.exe / lib / spamexperts.modules / smtplib.pyc (.txt) < prev    next >
Encoding:
Python Compiled Bytecode  |  2006-07-14  |  24.9 KB  |  824 lines

  1. # Source Generated with Decompyle++
  2. # File: in.pyc (Python 2.4)
  3.  
  4. '''SMTP/ESMTP client class.
  5.  
  6. This should follow RFC 821 (SMTP), RFC 1869 (ESMTP), RFC 2554 (SMTP
  7. Authentication) and RFC 2487 (Secure SMTP over TLS).
  8.  
  9. Notes:
  10.  
  11. Please remember, when doing ESMTP, that the names of the SMTP service
  12. extensions are NOT the same thing as the option keywords for the RCPT
  13. and MAIL commands!
  14.  
  15. Example:
  16.  
  17.   >>> import smtplib
  18.   >>> s=smtplib.SMTP("localhost")
  19.   >>> print s.help()
  20.   This is Sendmail version 8.8.4
  21.   Topics:
  22.       HELO    EHLO    MAIL    RCPT    DATA
  23.       RSET    NOOP    QUIT    HELP    VRFY
  24.       EXPN    VERB    ETRN    DSN
  25.   For more info use "HELP <topic>".
  26.   To report bugs in the implementation send email to
  27.       sendmail-bugs@sendmail.org.
  28.   For local information send email to Postmaster at your site.
  29.   End of HELP info
  30.   >>> s.putcmd("vrfy","someone@here")
  31.   >>> s.getreply()
  32.   (250, "Somebody OverHere <somebody@here.my.org>")
  33.   >>> s.quit()
  34. '''
  35. import socket
  36. import re
  37. import rfc822
  38. import base64
  39. import hmac
  40. from email.base64MIME import encode as encode_base64
  41. from sys import stderr
  42. __all__ = [
  43.     'SMTPException',
  44.     'SMTPServerDisconnected',
  45.     'SMTPResponseException',
  46.     'SMTPSenderRefused',
  47.     'SMTPRecipientsRefused',
  48.     'SMTPDataError',
  49.     'SMTPConnectError',
  50.     'SMTPHeloError',
  51.     'SMTPAuthenticationError',
  52.     'quoteaddr',
  53.     'quotedata',
  54.     'SMTP']
  55. SMTP_PORT = 25
  56. CRLF = '\r\n'
  57. OLDSTYLE_AUTH = re.compile('auth=(.*)', re.I)
  58.  
  59. class SMTPException(Exception):
  60.     '''Base class for all exceptions raised by this module.'''
  61.     pass
  62.  
  63.  
  64. class SMTPServerDisconnected(SMTPException):
  65.     '''Not connected to any SMTP server.
  66.  
  67.     This exception is raised when the server unexpectedly disconnects,
  68.     or when an attempt is made to use the SMTP instance before
  69.     connecting it to a server.
  70.     '''
  71.     pass
  72.  
  73.  
  74. class SMTPResponseException(SMTPException):
  75.     """Base class for all exceptions that include an SMTP error code.
  76.  
  77.     These exceptions are generated in some instances when the SMTP
  78.     server returns an error code.  The error code is stored in the
  79.     `smtp_code' attribute of the error, and the `smtp_error' attribute
  80.     is set to the error message.
  81.     """
  82.     
  83.     def __init__(self, code, msg):
  84.         self.smtp_code = code
  85.         self.smtp_error = msg
  86.         self.args = (code, msg)
  87.  
  88.  
  89.  
  90. class SMTPSenderRefused(SMTPResponseException):
  91.     """Sender address refused.
  92.  
  93.     In addition to the attributes set by on all SMTPResponseException
  94.     exceptions, this sets `sender' to the string that the SMTP refused.
  95.     """
  96.     
  97.     def __init__(self, code, msg, sender):
  98.         self.smtp_code = code
  99.         self.smtp_error = msg
  100.         self.sender = sender
  101.         self.args = (code, msg, sender)
  102.  
  103.  
  104.  
  105. class SMTPRecipientsRefused(SMTPException):
  106.     """All recipient addresses refused.
  107.  
  108.     The errors for each recipient are accessible through the attribute
  109.     'recipients', which is a dictionary of exactly the same sort as
  110.     SMTP.sendmail() returns.
  111.     """
  112.     
  113.     def __init__(self, recipients):
  114.         self.recipients = recipients
  115.         self.args = (recipients,)
  116.  
  117.  
  118.  
  119. class SMTPDataError(SMTPResponseException):
  120.     """The SMTP server didn't accept the data."""
  121.     pass
  122.  
  123.  
  124. class SMTPConnectError(SMTPResponseException):
  125.     '''Error during connection establishment.'''
  126.     pass
  127.  
  128.  
  129. class SMTPHeloError(SMTPResponseException):
  130.     '''The server refused our HELO reply.'''
  131.     pass
  132.  
  133.  
  134. class SMTPAuthenticationError(SMTPResponseException):
  135.     """Authentication error.
  136.  
  137.     Most probably the server didn't accept the username/password
  138.     combination provided.
  139.     """
  140.     pass
  141.  
  142.  
  143. class SSLFakeSocket:
  144.     '''A fake socket object that really wraps a SSLObject.
  145.  
  146.     It only supports what is needed in smtplib.
  147.     '''
  148.     
  149.     def __init__(self, realsock, sslobj):
  150.         self.realsock = realsock
  151.         self.sslobj = sslobj
  152.  
  153.     
  154.     def send(self, str):
  155.         self.sslobj.write(str)
  156.         return len(str)
  157.  
  158.     sendall = send
  159.     
  160.     def close(self):
  161.         self.realsock.close()
  162.  
  163.  
  164.  
  165. class SSLFakeFile:
  166.     '''A fake file like object that really wraps a SSLObject.
  167.  
  168.     It only supports what is needed in smtplib.
  169.     '''
  170.     
  171.     def __init__(self, sslobj):
  172.         self.sslobj = sslobj
  173.  
  174.     
  175.     def readline(self):
  176.         str = ''
  177.         chr = None
  178.         while chr != '\n':
  179.             chr = self.sslobj.read(1)
  180.             str += chr
  181.         return str
  182.  
  183.     
  184.     def close(self):
  185.         pass
  186.  
  187.  
  188.  
  189. def quoteaddr(addr):
  190.     '''Quote a subset of the email addresses defined by RFC 821.
  191.  
  192.     Should be able to handle anything rfc822.parseaddr can handle.
  193.     '''
  194.     m = (None, None)
  195.     
  196.     try:
  197.         m = rfc822.parseaddr(addr)[1]
  198.     except AttributeError:
  199.         pass
  200.  
  201.     if m == (None, None):
  202.         return '<%s>' % addr
  203.     elif m is None:
  204.         return '<>'
  205.     else:
  206.         return '<%s>' % m
  207.  
  208.  
  209. def quotedata(data):
  210.     """Quote data for email.
  211.  
  212.     Double leading '.', and change Unix newline '\\n', or Mac '\\r' into
  213.     Internet CRLF end-of-line.
  214.     """
  215.     return re.sub('(?m)^\\.', '..', re.sub('(?:\\r\\n|\\n|\\r(?!\\n))', CRLF, data))
  216.  
  217.  
  218. class SMTP:
  219.     """This class manages a connection to an SMTP or ESMTP server.
  220.     SMTP Objects:
  221.         SMTP objects have the following attributes:
  222.             helo_resp
  223.                 This is the message given by the server in response to the
  224.                 most recent HELO command.
  225.  
  226.             ehlo_resp
  227.                 This is the message given by the server in response to the
  228.                 most recent EHLO command. This is usually multiline.
  229.  
  230.             does_esmtp
  231.                 This is a True value _after you do an EHLO command_, if the
  232.                 server supports ESMTP.
  233.  
  234.             esmtp_features
  235.                 This is a dictionary, which, if the server supports ESMTP,
  236.                 will _after you do an EHLO command_, contain the names of the
  237.                 SMTP service extensions this server supports, and their
  238.                 parameters (if any).
  239.  
  240.                 Note, all extension names are mapped to lower case in the
  241.                 dictionary.
  242.  
  243.         See each method's docstrings for details.  In general, there is a
  244.         method of the same name to perform each SMTP command.  There is also a
  245.         method called 'sendmail' that will do an entire mail transaction.
  246.         """
  247.     debuglevel = 0
  248.     file = None
  249.     helo_resp = None
  250.     ehlo_resp = None
  251.     does_esmtp = 0
  252.     
  253.     def __init__(self, host = '', port = 0, local_hostname = None):
  254.         """Initialize a new instance.
  255.  
  256.         If specified, `host' is the name of the remote host to which to
  257.         connect.  If specified, `port' specifies the port to which to connect.
  258.         By default, smtplib.SMTP_PORT is used.  An SMTPConnectError is raised
  259.         if the specified `host' doesn't respond correctly.  If specified,
  260.         `local_hostname` is used as the FQDN of the local host.  By default,
  261.         the local hostname is found using socket.getfqdn().
  262.  
  263.         """
  264.         self.esmtp_features = { }
  265.         if host:
  266.             (code, msg) = self.connect(host, port)
  267.             if code != 220:
  268.                 raise SMTPConnectError(code, msg)
  269.             
  270.         
  271.         if local_hostname is not None:
  272.             self.local_hostname = local_hostname
  273.         else:
  274.             fqdn = socket.getfqdn()
  275.             if '.' in fqdn:
  276.                 self.local_hostname = fqdn
  277.             else:
  278.                 addr = socket.gethostbyname(socket.gethostname())
  279.                 self.local_hostname = '[%s]' % addr
  280.  
  281.     
  282.     def set_debuglevel(self, debuglevel):
  283.         '''Set the debug output level.
  284.  
  285.         A non-false value results in debug messages for connection and for all
  286.         messages sent to and received from the server.
  287.  
  288.         '''
  289.         self.debuglevel = debuglevel
  290.  
  291.     
  292.     def connect(self, host = 'localhost', port = 0):
  293.         """Connect to a host on a given port.
  294.  
  295.         If the hostname ends with a colon (`:') followed by a number, and
  296.         there is no port specified, that suffix will be stripped off and the
  297.         number interpreted as the port number to use.
  298.  
  299.         Note: This method is automatically invoked by __init__, if a host is
  300.         specified during instantiation.
  301.  
  302.         """
  303.         if not port and host.find(':') == host.rfind(':'):
  304.             i = host.rfind(':')
  305.             if i >= 0:
  306.                 host = host[:i]
  307.                 port = host[i + 1:]
  308.                 
  309.                 try:
  310.                     port = int(port)
  311.                 except ValueError:
  312.                     raise socket.error, 'nonnumeric port'
  313.                 except:
  314.                     None<EXCEPTION MATCH>ValueError
  315.                 
  316.  
  317.             None<EXCEPTION MATCH>ValueError
  318.         
  319.         if not port:
  320.             port = SMTP_PORT
  321.         
  322.         if self.debuglevel > 0:
  323.             print >>stderr, 'connect:', (host, port)
  324.         
  325.         msg = 'getaddrinfo returns an empty list'
  326.         self.sock = None
  327.         for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM):
  328.             (af, socktype, proto, canonname, sa) = res
  329.             
  330.             try:
  331.                 self.sock = socket.socket(af, socktype, proto)
  332.                 if self.debuglevel > 0:
  333.                     print >>stderr, 'connect:', (host, port)
  334.                 
  335.                 self.sock.connect(sa)
  336.             except socket.error:
  337.                 msg = None
  338.                 if self.debuglevel > 0:
  339.                     print >>stderr, 'connect fail:', (host, port)
  340.                 
  341.                 if self.sock:
  342.                     self.sock.close()
  343.                 
  344.                 self.sock = None
  345.                 continue
  346.  
  347.         
  348.         if not self.sock:
  349.             raise socket.error, msg
  350.         
  351.         (code, msg) = self.getreply()
  352.         if self.debuglevel > 0:
  353.             print >>stderr, 'connect:', msg
  354.         
  355.         return (code, msg)
  356.  
  357.     
  358.     def send(self, str):
  359.         """Send `str' to the server."""
  360.         if self.debuglevel > 0:
  361.             print >>stderr, 'send:', repr(str)
  362.         
  363.         if self.sock:
  364.             
  365.             try:
  366.                 self.sock.sendall(str)
  367.             except socket.error:
  368.                 self.close()
  369.                 raise SMTPServerDisconnected('Server not connected')
  370.             except:
  371.                 None<EXCEPTION MATCH>socket.error
  372.             
  373.  
  374.         None<EXCEPTION MATCH>socket.error
  375.         raise SMTPServerDisconnected('please run connect() first')
  376.  
  377.     
  378.     def putcmd(self, cmd, args = ''):
  379.         '''Send a command to the server.'''
  380.         if args == '':
  381.             str = '%s%s' % (cmd, CRLF)
  382.         else:
  383.             str = '%s %s%s' % (cmd, args, CRLF)
  384.         self.send(str)
  385.  
  386.     
  387.     def getreply(self):
  388.         """Get a reply from the server.
  389.  
  390.         Returns a tuple consisting of:
  391.  
  392.           - server response code (e.g. '250', or such, if all goes well)
  393.             Note: returns -1 if it can't read response code.
  394.  
  395.           - server response string corresponding to response code (multiline
  396.             responses are converted to a single, multiline string).
  397.  
  398.         Raises SMTPServerDisconnected if end-of-file is reached.
  399.         """
  400.         resp = []
  401.         if self.file is None:
  402.             self.file = self.sock.makefile('rb')
  403.         
  404.         while None:
  405.             line = self.file.readline()
  406.             if line == '':
  407.                 self.close()
  408.                 raise SMTPServerDisconnected('Connection unexpectedly closed')
  409.             
  410.             if self.debuglevel > 0:
  411.                 print >>stderr, 'reply:', repr(line)
  412.             
  413.             code = line[:3]
  414.             
  415.             try:
  416.                 errcode = int(code)
  417.             except ValueError:
  418.                 errcode = -1
  419.                 break
  420.  
  421.             if line[3:4] != '-':
  422.                 break
  423.                 continue
  424.         errmsg = '\n'.join(resp)
  425.         if self.debuglevel > 0:
  426.             print >>stderr, 'reply: retcode (%s); Msg: %s' % (errcode, errmsg)
  427.         
  428.         return (errcode, errmsg)
  429.  
  430.     
  431.     def docmd(self, cmd, args = ''):
  432.         '''Send a command, and return its response code.'''
  433.         self.putcmd(cmd, args)
  434.         return self.getreply()
  435.  
  436.     
  437.     def helo(self, name = ''):
  438.         """SMTP 'helo' command.
  439.         Hostname to send for this command defaults to the FQDN of the local
  440.         host.
  441.         """
  442.         if not name:
  443.             pass
  444.         self.putcmd('helo', self.local_hostname)
  445.         (code, msg) = self.getreply()
  446.         self.helo_resp = msg
  447.         return (code, msg)
  448.  
  449.     
  450.     def ehlo(self, name = ''):
  451.         """ SMTP 'ehlo' command.
  452.         Hostname to send for this command defaults to the FQDN of the local
  453.         host.
  454.         """
  455.         self.esmtp_features = { }
  456.         if not name:
  457.             pass
  458.         self.putcmd('ehlo', self.local_hostname)
  459.         (code, msg) = self.getreply()
  460.         if code == -1 and len(msg) == 0:
  461.             self.close()
  462.             raise SMTPServerDisconnected('Server not connected')
  463.         
  464.         self.ehlo_resp = msg
  465.         if code != 250:
  466.             return (code, msg)
  467.         
  468.         self.does_esmtp = 1
  469.         resp = self.ehlo_resp.split('\n')
  470.         del resp[0]
  471.         for each in resp:
  472.             auth_match = OLDSTYLE_AUTH.match(each)
  473.             if auth_match:
  474.                 self.esmtp_features['auth'] = self.esmtp_features.get('auth', '') + ' ' + auth_match.groups(0)[0]
  475.                 continue
  476.             
  477.             m = re.match('(?P<feature>[A-Za-z0-9][A-Za-z0-9\\-]*) ?', each)
  478.             if m:
  479.                 feature = m.group('feature').lower()
  480.                 params = m.string[m.end('feature'):].strip()
  481.                 if feature == 'auth':
  482.                     self.esmtp_features[feature] = self.esmtp_features.get(feature, '') + ' ' + params
  483.                 else:
  484.                     self.esmtp_features[feature] = params
  485.             feature == 'auth'
  486.         
  487.         return (code, msg)
  488.  
  489.     
  490.     def has_extn(self, opt):
  491.         '''Does the server support a given SMTP service extension?'''
  492.         return opt.lower() in self.esmtp_features
  493.  
  494.     
  495.     def help(self, args = ''):
  496.         """SMTP 'help' command.
  497.         Returns help text from server."""
  498.         self.putcmd('help', args)
  499.         return self.getreply()
  500.  
  501.     
  502.     def rset(self):
  503.         """SMTP 'rset' command -- resets session."""
  504.         return self.docmd('rset')
  505.  
  506.     
  507.     def noop(self):
  508.         """SMTP 'noop' command -- doesn't do anything :>"""
  509.         return self.docmd('noop')
  510.  
  511.     
  512.     def mail(self, sender, options = []):
  513.         """SMTP 'mail' command -- begins mail xfer session."""
  514.         optionlist = ''
  515.         if options and self.does_esmtp:
  516.             optionlist = ' ' + ' '.join(options)
  517.         
  518.         self.putcmd('mail', 'FROM:%s%s' % (quoteaddr(sender), optionlist))
  519.         return self.getreply()
  520.  
  521.     
  522.     def rcpt(self, recip, options = []):
  523.         """SMTP 'rcpt' command -- indicates 1 recipient for this mail."""
  524.         optionlist = ''
  525.         if options and self.does_esmtp:
  526.             optionlist = ' ' + ' '.join(options)
  527.         
  528.         self.putcmd('rcpt', 'TO:%s%s' % (quoteaddr(recip), optionlist))
  529.         return self.getreply()
  530.  
  531.     
  532.     def data(self, msg):
  533.         """SMTP 'DATA' command -- sends message data to server.
  534.  
  535.         Automatically quotes lines beginning with a period per rfc821.
  536.         Raises SMTPDataError if there is an unexpected reply to the
  537.         DATA command; the return value from this method is the final
  538.         response code received when the all data is sent.
  539.         """
  540.         self.putcmd('data')
  541.         (code, repl) = self.getreply()
  542.         if self.debuglevel > 0:
  543.             print >>stderr, 'data:', (code, repl)
  544.         
  545.         if code != 354:
  546.             raise SMTPDataError(code, repl)
  547.         else:
  548.             q = quotedata(msg)
  549.             if q[-2:] != CRLF:
  550.                 q = q + CRLF
  551.             
  552.             q = q + '.' + CRLF
  553.             self.send(q)
  554.             (code, msg) = self.getreply()
  555.             if self.debuglevel > 0:
  556.                 print >>stderr, 'data:', (code, msg)
  557.             
  558.             return (code, msg)
  559.  
  560.     
  561.     def verify(self, address):
  562.         """SMTP 'verify' command -- checks for address validity."""
  563.         self.putcmd('vrfy', quoteaddr(address))
  564.         return self.getreply()
  565.  
  566.     vrfy = verify
  567.     
  568.     def expn(self, address):
  569.         """SMTP 'verify' command -- checks for address validity."""
  570.         self.putcmd('expn', quoteaddr(address))
  571.         return self.getreply()
  572.  
  573.     
  574.     def login(self, user, password):
  575.         """Log in on an SMTP server that requires authentication.
  576.  
  577.         The arguments are:
  578.             - user:     The user name to authenticate with.
  579.             - password: The password for the authentication.
  580.  
  581.         If there has been no previous EHLO or HELO command this session, this
  582.         method tries ESMTP EHLO first.
  583.  
  584.         This method will return normally if the authentication was successful.
  585.  
  586.         This method may raise the following exceptions:
  587.  
  588.          SMTPHeloError            The server didn't reply properly to
  589.                                   the helo greeting.
  590.          SMTPAuthenticationError  The server didn't accept the username/
  591.                                   password combination.
  592.          SMTPException            No suitable authentication method was
  593.                                   found.
  594.         """
  595.         
  596.         def encode_cram_md5(challenge, user, password):
  597.             challenge = base64.decodestring(challenge)
  598.             response = user + ' ' + hmac.HMAC(password, challenge).hexdigest()
  599.             return encode_base64(response, eol = '')
  600.  
  601.         
  602.         def encode_plain(user, password):
  603.             return encode_base64('%s\x00%s\x00%s' % (user, user, password), eol = '')
  604.  
  605.         AUTH_PLAIN = 'PLAIN'
  606.         AUTH_CRAM_MD5 = 'CRAM-MD5'
  607.         AUTH_LOGIN = 'LOGIN'
  608.         if self.helo_resp is None and self.ehlo_resp is None:
  609.             if self.ehlo()[0] <= self.ehlo()[0]:
  610.                 pass
  611.             elif not self.ehlo()[0] <= 299:
  612.                 (code, resp) = self.helo()
  613.                 if code <= code:
  614.                     pass
  615.                 elif not code <= 299:
  616.                     raise SMTPHeloError(code, resp)
  617.                 
  618.             
  619.         
  620.         if not self.has_extn('auth'):
  621.             raise SMTPException('SMTP AUTH extension not supported by server.')
  622.         
  623.         authlist = self.esmtp_features['auth'].split()
  624.         preferred_auths = [
  625.             AUTH_CRAM_MD5,
  626.             AUTH_PLAIN,
  627.             AUTH_LOGIN]
  628.         authmethod = None
  629.         for method in preferred_auths:
  630.             if method in authlist:
  631.                 authmethod = method
  632.                 break
  633.                 continue
  634.         
  635.         if authmethod == AUTH_CRAM_MD5:
  636.             (code, resp) = self.docmd('AUTH', AUTH_CRAM_MD5)
  637.             if code == 503:
  638.                 return (code, resp)
  639.             
  640.             (code, resp) = self.docmd(encode_cram_md5(resp, user, password))
  641.         elif authmethod == AUTH_PLAIN:
  642.             (code, resp) = self.docmd('AUTH', AUTH_PLAIN + ' ' + encode_plain(user, password))
  643.         elif authmethod == AUTH_LOGIN:
  644.             (code, resp) = self.docmd('AUTH', '%s %s' % (AUTH_LOGIN, encode_base64(user, eol = '')))
  645.             if code != 334:
  646.                 raise SMTPAuthenticationError(code, resp)
  647.             
  648.             (code, resp) = self.docmd(encode_base64(password, eol = ''))
  649.         elif authmethod is None:
  650.             raise SMTPException('No suitable authentication method found.')
  651.         
  652.         if code not in [
  653.             235,
  654.             503]:
  655.             raise SMTPAuthenticationError(code, resp)
  656.         
  657.         return (code, resp)
  658.  
  659.     
  660.     def starttls(self, keyfile = None, certfile = None):
  661.         '''Puts the connection to the SMTP server into TLS mode.
  662.  
  663.         If the server supports TLS, this will encrypt the rest of the SMTP
  664.         session. If you provide the keyfile and certfile parameters,
  665.         the identity of the SMTP server and client can be checked. This,
  666.         however, depends on whether the socket module really checks the
  667.         certificates.
  668.         '''
  669.         (resp, reply) = self.docmd('STARTTLS')
  670.         if resp == 220:
  671.             sslobj = socket.ssl(self.sock, keyfile, certfile)
  672.             self.sock = SSLFakeSocket(self.sock, sslobj)
  673.             self.file = SSLFakeFile(sslobj)
  674.         
  675.         return (resp, reply)
  676.  
  677.     
  678.     def sendmail(self, from_addr, to_addrs, msg, mail_options = [], rcpt_options = []):
  679.         '''This command performs an entire mail transaction.
  680.  
  681.         The arguments are:
  682.             - from_addr    : The address sending this mail.
  683.             - to_addrs     : A list of addresses to send this mail to.  A bare
  684.                              string will be treated as a list with 1 address.
  685.             - msg          : The message to send.
  686.             - mail_options : List of ESMTP options (such as 8bitmime) for the
  687.                              mail command.
  688.             - rcpt_options : List of ESMTP options (such as DSN commands) for
  689.                              all the rcpt commands.
  690.  
  691.         If there has been no previous EHLO or HELO command this session, this
  692.         method tries ESMTP EHLO first.  If the server does ESMTP, message size
  693.         and each of the specified options will be passed to it.  If EHLO
  694.         fails, HELO will be tried and ESMTP options suppressed.
  695.  
  696.         This method will return normally if the mail is accepted for at least
  697.         one recipient.  It returns a dictionary, with one entry for each
  698.         recipient that was refused.  Each entry contains a tuple of the SMTP
  699.         error code and the accompanying error message sent by the server.
  700.  
  701.         This method may raise the following exceptions:
  702.  
  703.          SMTPHeloError          The server didn\'t reply properly to
  704.                                 the helo greeting.
  705.          SMTPRecipientsRefused  The server rejected ALL recipients
  706.                                 (no mail was sent).
  707.          SMTPSenderRefused      The server didn\'t accept the from_addr.
  708.          SMTPDataError          The server replied with an unexpected
  709.                                 error code (other than a refusal of
  710.                                 a recipient).
  711.  
  712.         Note: the connection will be open even after an exception is raised.
  713.  
  714.         Example:
  715.  
  716.          >>> import smtplib
  717.          >>> s=smtplib.SMTP("localhost")
  718.          >>> tolist=["one@one.org","two@two.org","three@three.org","four@four.org"]
  719.          >>> msg = \'\'\'\\
  720.          ... From: Me@my.org
  721.          ... Subject: testin\'...
  722.          ...
  723.          ... This is a test \'\'\'
  724.          >>> s.sendmail("me@my.org",tolist,msg)
  725.          { "three@three.org" : ( 550 ,"User unknown" ) }
  726.          >>> s.quit()
  727.  
  728.         In the above example, the message was accepted for delivery to three
  729.         of the four addresses, and one was rejected, with the error code
  730.         550.  If all addresses are accepted, then the method will return an
  731.         empty dictionary.
  732.  
  733.         '''
  734.         if self.helo_resp is None and self.ehlo_resp is None:
  735.             if self.ehlo()[0] <= self.ehlo()[0]:
  736.                 pass
  737.             elif not self.ehlo()[0] <= 299:
  738.                 (code, resp) = self.helo()
  739.                 if code <= code:
  740.                     pass
  741.                 elif not code <= 299:
  742.                     raise SMTPHeloError(code, resp)
  743.                 
  744.             
  745.         
  746.         esmtp_opts = []
  747.         if self.does_esmtp:
  748.             if self.has_extn('size'):
  749.                 esmtp_opts.append('size=%d' % len(msg))
  750.             
  751.             for option in mail_options:
  752.                 esmtp_opts.append(option)
  753.             
  754.         
  755.         (code, resp) = self.mail(from_addr, esmtp_opts)
  756.         if code != 250:
  757.             self.rset()
  758.             raise SMTPSenderRefused(code, resp, from_addr)
  759.         
  760.         senderrs = { }
  761.         if isinstance(to_addrs, basestring):
  762.             to_addrs = [
  763.                 to_addrs]
  764.         
  765.         for each in to_addrs:
  766.             (code, resp) = self.rcpt(each, rcpt_options)
  767.             if code != 250 and code != 251:
  768.                 senderrs[each] = (code, resp)
  769.                 continue
  770.         
  771.         if len(senderrs) == len(to_addrs):
  772.             self.rset()
  773.             raise SMTPRecipientsRefused(senderrs)
  774.         
  775.         (code, resp) = self.data(msg)
  776.         if code != 250:
  777.             self.rset()
  778.             raise SMTPDataError(code, resp)
  779.         
  780.         return senderrs
  781.  
  782.     
  783.     def close(self):
  784.         '''Close the connection to the SMTP server.'''
  785.         if self.file:
  786.             self.file.close()
  787.         
  788.         self.file = None
  789.         if self.sock:
  790.             self.sock.close()
  791.         
  792.         self.sock = None
  793.  
  794.     
  795.     def quit(self):
  796.         '''Terminate the SMTP session.'''
  797.         self.docmd('quit')
  798.         self.close()
  799.  
  800.  
  801. if __name__ == '__main__':
  802.     import sys
  803.     
  804.     def prompt(prompt):
  805.         sys.stdout.write(prompt + ': ')
  806.         return sys.stdin.readline().strip()
  807.  
  808.     fromaddr = prompt('From')
  809.     toaddrs = prompt('To').split(',')
  810.     print 'Enter message, end with ^D:'
  811.     msg = ''
  812.     while 1:
  813.         line = sys.stdin.readline()
  814.         if not line:
  815.             break
  816.         
  817.         msg = msg + line
  818.     print 'Message length is %d' % len(msg)
  819.     server = SMTP('localhost')
  820.     server.set_debuglevel(1)
  821.     server.sendmail(fromaddr, toaddrs, msg)
  822.     server.quit()
  823.  
  824.